<?php
/**
 * ZFEngine
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://zfengine.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zfengine.com so we can send you a copy immediately.
 *
 * @uses        ZFEngine_Application_Exception
 * @category    ZFEngine
 * @package     ZFEngine_Application
 * @copyright  Copyright (c) 2009-2010 Stepan Tanasiychuk (http://stfalcon.com)
 * @license     http://zfengine.com/license/new-bsd     New BSD License
 * @version     $Id$
 */

/**
 * Resource for initializing Doctrine
 *
 * Original source copied from http://github.com/mlurz71/parables/blob/master/Parables/Application/Resource/Doctrine.php
 *
 * @uses        ZFEngine_Application_Exception
 * @category    ZFEngine
 * @package     ZFEngine_Application
 * @copyright  Copyright (c) 2009-2010 Stepan Tanasiychuk (http://stfalcon.com)
 * @license     http://zfengine.com/license/new-bsd     New BSD License
 */
class ZFEngine_Application_Resource_Doctrine extends Zend_Application_Resource_ResourceAbstract
{

    /**
     * Build DSN string from an array
     *
     * @param   array $dsn
     * @return  string
     */
    protected function _buildDsnFromArray(array $dsn)
    {
        $options = null;
        if (array_key_exists('options', $dsn)) {
            $options = http_build_query($dsn['options']);
        }

        return sprintf('%s://%s:%s@%s/%s?%s',
            $dsn['adapter'],
            $dsn['username'],
            $dsn['password'],
            $dsn['host'],
            $dsn['dbname'],
            $options);
    }

    /**
     * Set attributes for a Doctrine_Configurable instance
     *
     * @param   Doctrine_Configurable $object
     * @param   array $attributes
     * @return  void
     * @throws  Zend_Application_Resource_Exception
     */
    protected function _setAttributes(Doctrine_Configurable $object, array $attributes)
    {
        $reflect = new ReflectionClass('Doctrine_Core');
        $doctrineConstants = $reflect->getConstants();

        $attributes = array_change_key_case($attributes, CASE_UPPER);

        foreach ($attributes as $key => $value) {
            if (!array_key_exists($key, $doctrineConstants)) {
                throw new Zend_Application_Resource_Exception("Invalid attribute $key.");
            }

            $attrIdx = $doctrineConstants[$key];
            $attrVal = $value;

            if (Doctrine_Core::ATTR_QUERY_CACHE == $attrIdx) {
                $attrVal = $this->_getCache($value);
            } elseif (Doctrine_Core::ATTR_RESULT_CACHE == $attrIdx) {
                $attrVal = $this->_getCache($value);
            } else {
                if (is_string($value)) {
                    $value = strtoupper($value);
                    if (array_key_exists($value, $doctrineConstants)) {
                        $attrVal = $doctrineConstants[$value];
                    }
                }
            }

            $object->setAttribute($attrIdx, $attrVal);
        }
    }

    /**
     * Set connection listeners
     *
     * @param   Doctrine_Connection_Common $conn
     * @param   array $options
     * @return  void
     * @throws  Zend_Application_Resource_Exception
     */
    protected function _setConnectionListeners(Doctrine_Connection_Common $conn, array $options)
    {
        foreach ($options as $alias => $class) {
            if (!class_exists($class)) {
                throw new Zend_Application_Resource_Exception("$class does not exist.");
            }

            $conn->addListener(new $class(), $alias);
        }
    }

    /**
     * Retrieve a Doctrine_Cache instance
     *
     * @param   array $options
     * @return  Doctrine_Cache
     * @throws  Zend_Application_Resource_Exception
     */
    protected function _getCache(array $options)
    {
        if (!array_key_exists('driver', $options)) {
            throw new Zend_Application_Resource_Exception('Undefined cache driver.');
        }

        switch ($options['driver'])
        {
            case 'apc':
                return new Doctrine_Cache_Apc();

            case 'db':
                if (!array_key_exists('options', $options)) {
                    throw new Zend_Application_Resource_Exception('Undefined db cache options.');
                }

                if (empty($options['options'])) {
                    throw new Zend_Application_Resource_Exception('Invalid db cache options.');
                }

                if (!array_key_exists('dsn', $options['options'])) {
                    throw new Zend_Application_Resource_Exception("Undefined db cache DSN.");
                }

                if (empty($options['options']['dsn'])) {
                    throw new Zend_Application_Resource_Exception("Invalid db cache DSN.");
                }

                if (!array_key_exists('tableName', $options['options'])) {
                    throw new Zend_Application_Resource_Exception("Undefined db cache table name.");
                }

                if (empty($options['options']['tableName'])) {
                    throw new Zend_Application_Resource_Exception("Invalid db cache table name.");
                }

                $dsn = (is_array($options['options']['dsn']))
                    ? $this->_buildDsnFromArray($options['options']['dsn'])
                    : $options['options']['dsn'];

                $cacheConn = Doctrine_Manager::connection($dsn);

                $cache = new Doctrine_Cache_Db(array(
                    'connection' => $cacheConn,
                    'tableName' => $options['options']['tableName'],
                ));

                return $cache;

            case 'memcache':
                if (!array_key_exists('options', $options)) {
                    throw new Zend_Application_Resource_Exception('Undefined memcache options.');
                }

                if (empty($options['options'])) {
                    throw new Zend_Application_Resource_Exception('Invalid memcache options.');
                }

                return new Doctrine_Cache_Memcache($options['options']);

            case 'xcache':
                return new Doctrine_Cache_Xcache();

            default:
                throw new Zend_Application_Resource_Exception('Unsupported cache driver.');
        }
    }

    /**
     * Set models directory
     *
     * @param   array $options
     * @return  void
     */
    protected function _setModelsDirectory(array $options)
    {
        $path = $options['models_path'];
        $path = realpath($path);

        if (!is_dir($path)) {
            throw new Zend_Application_Resource_Exception("$path does not exist.");
        }

        /*
         * Это самый настроящий костыль.
         * Без него, при использовании model_loading_conservative не будут
         * подгружаться базовые модели с models/Base.
         * При model_loading_pear возникает другая проблема - базовые модели
         * подгружаются нормально, но возникает проблема с generate-migration-diff.
         * Соответсвующие тикеты уже созданы в багтреке доктрины
         */
        if ($options['attributes']['attr_model_loading']  != 'model_loading_pear') {
            Doctrine_Core::loadModels($path);
        } else {
            Doctrine_Core::setModelsDirectory($path);
        }

    }

    /**
     * Set manager level attributes
     *
     * @param   array $options
     * @return  ZFEngine_Application_Resource_Doctrine
     */
    public function setManager(array $options)
    {
        $options = array_change_key_case($options, CASE_LOWER);
        if (array_key_exists('attributes', $options)) {
            $this->_setAttributes(
                Doctrine_Manager::getInstance(),
                $options['attributes']
            );
        }
        if (array_key_exists('models_path', $options)) {
            $this->_setModelsDirectory($options);
        }

        return $this;
    }

    /**
     * Set connections and connection level attributes
     *
     * @param   array $options
     * @return  ZFEngine_Application_Resource_Doctrine
     * @throws  Zend_Application_Resource_Exception
     */
    public function setConnections(array $options)
    {
        $options = array_change_key_case($options, CASE_LOWER);

        foreach($options as $key => $value) {
            if (!is_array($value)) {
                throw new Zend_Application_Resource_Exception("Invalid connection on $key.");
            }

            if (!array_key_exists('dsn', $value)) {
                throw new Zend_Application_Resource_Exception("Undefined DSN on $key.");
            }

            if (empty($value['dsn'])) {
                throw new Zend_Application_Resource_Exception("Invalid DSN on $key.");
            }

            $dsn = (is_array($value['dsn']))
                ? $this->_buildDsnFromArray($value['dsn'])
                : $value['dsn'];

            $connection = Doctrine_Manager::connection($dsn, $key);

            if (array_key_exists('attributes', $value)) {
                $this->_setAttributes($connection, $value['attributes']);
            }

            if (array_key_exists('listeners', $value)) {
                $this->_setConnectionListeners($connection, $value['listeners']);
            }

            if (array_key_exists('options', $value)) {
                if ($value['options']['collate']) {
                    $connection->setCollate($value['options']['collate']);
                }
                if ($value['options']['charset']) {
                    $connection->setCharset($value['options']['charset']);
                }
            }
        }

        return $this;
    }

    /**
     * Defined by Zend_Application_Resource_Resource
     *
     * @return  ZFEngine_Application_Resource_Doctrine
     * @throws  Zend_Application_Resource_Exception
     */
    public function init()
    {
        $this->getBootstrap()->getApplication()->getAutoloader()
             ->pushAutoloader(array('Doctrine_Core', 'autoload'));

        $this->getBootstrap()->getApplication()->getAutoloader()
             ->pushAutoloader(array('Doctrine_Core', 'modelsAutoload'));

        return $this;
    }
}